<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Logic UI</title>
    <style>
        body {
            margin: 0;
            height: 100vh;
            overflow: hidden;
            font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;
            background-color: #212121;
            color: #fff;
            display: flex;
            flex-direction: column;
        }

        #controls-bar {
            background: #363636;
            padding: 8px 12px;
            display: flex;
            align-items: center;
            gap: 12px;
            flex-shrink: 0;
        }

        select, button {
            background: #212121;
            color: #fff;
            border: 1px solid #555;
            border-radius: 4px;
            padding: 4px 8px;
            font-size: 0.9em;
            font-family: inherit;
        }

        button:disabled {
            opacity: 0.35;
            color: #888;
            cursor: not-allowed;
        }

        #result {
            color: #aaa;
            font-size: 0.85em;
            flex: 1;
        }

        #blocklyDiv {
            flex: 1;
            min-height: 0;
            width: 100%;
        }
    </style>
</head>
<body>
    <div id="controls-bar">
        <input id="optaip" name="optaip" type="hidden" size="25" onChange="changeIp(this.value)"/>
        <select id="slots" name="slots" onChange="slotSelected(this.value)">
            <option value="">Logic slots</option>
        </select>
        <button type="button" id="loadCode" onclick="loadDefinition()"><svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" style="vertical-align:middle;margin-right:5px;transform:translateY(-2px)"><path d="M1 3.5A1.5 1.5 0 0 1 2.5 2h2.764c.958 0 1.76.56 2.311 1.184C7.985 3.648 8.48 4 9 4h4.5A1.5 1.5 0 0 1 15 5.5v.64c.57.265.94.876.856 1.546l-.64 5.124A2.5 2.5 0 0 1 12.733 15H3.266a2.5 2.5 0 0 1-2.481-2.19l-.64-5.124A1.5 1.5 0 0 1 1 6.14V3.5zM2 6h12v-.5a.5.5 0 0 0-.5-.5H9c-.964 0-1.71-.629-2.174-1.154C6.374 3.334 5.82 3 5.264 3H2.5a.5.5 0 0 0-.5.5V6zm-.367 1a.5.5 0 0 0-.496.562l.64 5.124A1.5 1.5 0 0 0 3.266 14h9.468a1.5 1.5 0 0 0 1.489-1.314l.64-5.124A.5.5 0 0 0 14.367 7H1.633z"/></svg>Load</button>
        <button type="button" id="generateCode" onclick="doGenerate()"><svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" style="vertical-align:middle;margin-right:5px;transform:translateY(-2px)"><path d="M12 2h-2v3h2V2z"/><path d="M2.5 0A2.5 2.5 0 0 0 0 2.5v11A2.5 2.5 0 0 0 2.5 16h11a2.5 2.5 0 0 0 2.5-2.5v-9.5l-3-3H2.5zM10 4H5a1 1 0 0 1-1-1V1h7v2a1 1 0 0 1-1 1zm2 7.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/></svg>Save</button>
        <div id="result">&nbsp;</div>
    </div>
    
    <div id="blocklyDiv"></div>

    <!-- Load Blockly core -->
    <script src="./blockly_compressed.js"></script>
    <!-- Load the default blocks -->
    <script src="./blocks_compressed.js"></script>
    <!-- Load a generator -->
    <script src="./lua_compressed.js"></script>
    <!-- Load a message file -->
    <script src="./msg/en.js"></script>
    <script src="./zepto.min.js"></script>
    <script>
    //Blockly.setLocale(En);

    const toolbox = {
        // There are two kinds of toolboxes. The simpler one is a flyout toolbox.
        kind:  "flyoutToolbox",
        // The contents is the blocks and other items that exist in your toolbox.
        contents: [
           // {
           //     kind: "category",
           //     name: "Toolbox",
           //     contents: [

                    {
                        kind: 'block',
                        type: 'controls_if'
                    },
                    // {
                    //     kind: 'block',
                    //     type: 'controls_whileUntil'
                    // },

                    {
                        "kind": "block",
                        "type": "logic_compare",
                    },
                    {
                        "kind": "block",
                        "type": "logic_operation"
                    },
                    // {
                    //     "kind": "block",
                    //     "type": "logic_negate"
                    // },
                    /*
                    {
                        "kind": "block",
                        "type": "logic_null"
                    },
                     */

            //     ]
            // },
            /*
            {
                kind: "category",
                name: "Math",
                contents: [

                    {
                    kind: 'block',
                    type: 'math_change'
                },
                    {
                        kind: 'block',
                        type: 'math_number_property'
                    },
                    {
                        kind: 'block',
                        type: 'math_arithmetic'
                    },

                ]
            },
             */
            // {
            //     kind: "category",
            //     name: "Functions",
            //     contents: [
                    { kind: 'block',
                        type: 'delay'
                    },
                    { kind: 'block',
                        type: 'millis'
                    },
            //     ]
            // },
            // {
            //     kind: "category",
            //     name: "Variables/Values",
            //     contents: [
                    {
                        "kind": "block",
                        "type":"math_number"
                    },
          /*
          {
                        "kind": "block",
                        "type":"text"
                    },
*/
                    { kind: 'block',
                        type: 'logic_digital_value'
                    },

/*                    {
                        kind: 'block',
                        type: 'variables_get'
                    },
                    {
                        kind: 'block',
                        type: 'variables_set'
                    },

                    {
                        "kind": "block",
                        "type":"text_append"
                    }*/
            //     ]
            // },
            // {
            //     kind: "category",
            //     name: "Link70",
            //     contents: [
                    {
                        kind: 'block',
                        type: 'digital_read'
                    },
                    { kind: 'block',
                        type: 'digital_write'
                    },
                    { kind: 'block',
                        type: 'analog_read'
                    },
                    { kind: 'block',
                        type: 'analog_read_volts'
                    },
                    { kind: 'block',
                        type: 'time_units'
                    },
                    {
                        kind: 'block',
                        type: 'digital_inputs'
                    },
                    { kind: 'block',
                        type: 'digital_outputs'
                    },
                    { kind: 'block',
                        type: 'analog_inputs'
                    },
          //      ]
          //  },
          /*  {
                kind: "category",
                name: "Debug",
                contents: [
                    { kind: 'block',
                        type: 'print_message'
                    },
                ]
            }, */
            // You can add more blocks to this array.
        ]
    };




    Blockly.Blocks['delay'] = {
        init: function() {
            this.appendValueInput('COUNT')
                .setCheck('Number')
                .appendField('Delay');
            this.appendValueInput('UNIT')
                .setCheck('TimeUnit')
                .appendField("Time Units");
            this.setColour(160);
            this.setTooltip('Wait a given amount of time');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
            this.setStyle('procedure_blocks');
            this.setPreviousStatement(true);
            this.setNextStatement(true);
        }
    };

    Blockly.Blocks['digital_write'] = {
        init: function() {
            this.appendValueInput('PORT')
                .setCheck('DigitalPort')
                .appendField('Change port');
            this.appendValueInput('VALUE')
                .setCheck('Boolean')
                .appendField("to state");
            this.setColour(160);
            this.setTooltip('Changess the state of the selected port');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
            this.setStyle('procedure_blocks');
            this.setPreviousStatement(true);
            this.setNextStatement(true);
        }
    };

    Blockly.Blocks['digital_read'] = {
        init: function () {
            this.appendValueInput('PORT')
                .setCheck('DigitalPort')
                .appendField('state of port');
            this.setOutput(true, 'Boolean');
            this.setColour(160);
            this.setTooltip('Returns the state of the selected port');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
        }
    };
    Blockly.Blocks['analog_read'] = {
        init: function () {
            this.appendValueInput('PORT')
                .setCheck('AnalogPort')
                .appendField('value of analog port');
            this.setOutput(true, 'Number');
            this.setColour(160);
            this.setTooltip('Returns the value of the selected analog input');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
        }
    };

    Blockly.Blocks['analog_read_volts'] = {
        init: function () {
            this.appendValueInput('PORT')
                .setCheck('AnalogPort')
                .appendField('voltage of analog port');
            this.setOutput(true, 'Number');
            this.setColour(160);
            this.setTooltip('Returns the voltage of the selected analog input');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
        }
    };


    Blockly.Blocks['print_message'] = {
        init: function () {
            this.appendValueInput('VALUE')
//                .setCheck('AnalogPort')
                .appendField('Print message');
//            this.setOutput(true, 'Number');
            this.setColour(160);
            this.setTooltip('Prints a message');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
            this.setPreviousStatement(true);
            this.setNextStatement(true);
        }
    };

    Blockly.Blocks['millis'] = {
        init: function () {
            this.appendDummyInput('VALUE')
                .appendField("Milliseconds since start"); // Add a simple text field
            this.setOutput(true, 'Number')
            this.setColour(160);
            this.setTooltip('Returns the number of milliseconds since startup');
            this.setHelpUrl('http://www.w3schools.com/jsref/jsref_length_string.asp');
        }
    };

    // Create the definition.
    const definitions = Blockly.common.createBlockDefinitionsFromJsonArray([
        {
            "type": "digital_inputs",
            "message0": "Input: %1",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "DIGITAL_INPUT",
                    "options": [
                        /* TODO should we filter the inputs running in analog mode? Technically that's a
                        *   valid operation, but might cause confusion. */
                        [ "Input 1", "I1" ],
                        [ "Input 2", "I2" ],
                        [ "Input 3", "I3" ],
                        [ "Input 4", "I4" ],
                        [ "Input 5", "I5" ],
                        [ "Input 6", "I6" ],
                        [ "Input 7", "I7" ],
                        [ "Input 8", "I8" ],
                        [ "Relay 1", "RELAY1" ],
                        [ "Relay 2", "RELAY2" ],
                        [ "Relay 3", "RELAY3" ],
                        [ "Relay 4", "RELAY4" ],
                        ["LED 1", "LED_D0"],
                        ["LED 2", "LED_D1"],
                        ["LED 3", "LED_D2"],
                        ["LED 4", "LED_D3"]
                    ]
                }
            ],
            'output': 'DigitalPort',
            'style': 'logic_blocks'
        },
        {
            "type": "analog_inputs",
            "message0": "Analog Input: %1",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "ANALOG_INPUT",
                    "options": [
                        /* TODO should we only include those ports running in analog mode? */
                        [ "Analog 1", "I1" ],
                        [ "Analog 2", "I2" ],
                        [ "Analog 3", "I3" ],
                        [ "Analog 4", "I4" ],
                        [ "Analog 5", "I5" ],
                        [ "Analog 6", "I6" ],
                        [ "Analog 7", "I7" ],
                        [ "Analog 8", "I8" ]
                    ]
                }
            ],
            'output': 'AnalogPort',
            'style': 'logic_blocks'
        },
        {
            "type": "digital_outputs",
            "message0": "Output %1",
            "args0": [
                {
                    "type": "field_dropdown",
                    "name": "DIGITAL_OUTPUT",
                    "options": [
                        [ "Relay 1", "RELAY1" ],
                        [ "Relay 2", "RELAY2" ],
                        [ "Relay 3", "RELAY3" ],
                        [ "Relay 4", "RELAY4" ],
                        ["LED 1", "LED_D0"],
                        ["LED 2", "LED_D1"],
                        ["LED 3", "LED_D2"],
                        ["LED 4", "LED_D3"]
                    ]
                }
            ],
            'output': 'DigitalPort',
            'style': 'logic_blocks'
        },
        {
            'type': 'logic_digital_value',
            'message0': '%1',
            'args0': [
                {
                    'type': 'field_dropdown',
                    'name': 'BOOL',
                    'options': [
                        ['ON', '1'],
                        ['OFF', '0'],
                    ],
                },
            ],
            'output': 'Boolean',
            'style': 'logic_blocks'
        },
        {
            'type': 'time_units',
            'message0': '%1',
            'args0': [
                {
                    'type': 'field_dropdown',
                    'name': 'UNIT',
                    'options': [
                        ['MilliSeconds', '1'],
                        ['Seconds', '1000'],
                    ],
                },
            ],
            'output': 'TimeUnit',
            'style': 'logic_blocks'
        },
        {
            // The type is like the "class name" for your block. It is used to construct
            // new instances. E.g. in the toolbox.
            type: 'my_custom_block',
            // The message defines the basic text of your block, and where inputs or
            // fields will be inserted.
            message0: 'move forward %1',
            args0: [
                // Each arg is associated with a %# in the message.
                // This one gets substituted for %1.
                {
                    // The type specifies the kind of input or field to be inserted.
                    type: 'field_number',
                    // The name allows you to reference the field and get its value.
                    name: 'FIELD_NAME',
                }
            ],
            // Adds an untyped previous connection to the top of the block.
            previousStatement: null,
            // Adds an untyped next connection to the bottom of the block.
            nextStatement: null,
        }
    ]);



    Blockly.Lua.forBlock['delay'] =  function(block,generator) {
        console.log(block);
        count = generator.valueToCode(block, 'COUNT', Blockly.Lua.ORDER_ATOMIC) || '';
        unit = generator.valueToCode(block, 'UNIT', Blockly.Lua.ORDER_ATOMIC) || '';
        return "opta.delay(" + count + " * " + unit + ")\n";
    };


    Blockly.Lua.forBlock['digital_read'] =  function(block,generator) {
        console.log(block);
        returnValue = generator.valueToCode(block, 'PORT', Blockly.Lua.ORDER_ATOMIC) || '';
        return ["opta.digitalRead(" + returnValue + ")", Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['digital_write'] =  function(block,generator) {
        console.log(block);
        value = generator.valueToCode(block, 'VALUE', Blockly.Lua.ORDER_ATOMIC) || '';
        port = generator.valueToCode(block, 'PORT', Blockly.Lua.ORDER_ATOMIC) || '';
        return "opta.digitalWrite(" + port + ", " + value + ")\n";
    };

    Blockly.Lua.forBlock['analog_read'] =  function(block,generator) {
        console.log(block);
        returnValue = generator.valueToCode(block, 'PORT', Blockly.Lua.ORDER_ATOMIC) || '';
        return ["opta.analogRead(" + returnValue + ")", Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['analog_read_volts'] =  function(block,generator) {
        console.log(block);
        returnValue = generator.valueToCode(block, 'PORT', Blockly.Lua.ORDER_ATOMIC) || '';
        return ["opta.analogReadVolts(" + returnValue + ")", Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['print_message'] =  function(block,generator) {
        console.log(block);
        returnValue = generator.valueToCode(block, 'VALUE', Blockly.Lua.ORDER_ATOMIC) || '';
        return "print(\"lua: \" .. " + returnValue + " .. \"\\n\")\n";
    };

    Blockly.Lua.forBlock['millis'] =  function(block,generator) {
        console.log(block);
//        returnValue = generator.valueToCode(block, 'VALUE', Blockly.Lua.ORDER_ATOMIC) || '';
        return ["opta.millis()", Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['time_units'] =  function(block,generator) {
        return [block.getFieldValue("UNIT"), Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['logic_digital_value'] = function(block, generator) {
        return [ block.getFieldValue("BOOL"), Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['digital_outputs'] = function(block,generator) {
        return [block.getFieldValue("DIGITAL_OUTPUT"), Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['digital_inputs'] = function(block,generator) {
        return [block.getFieldValue("DIGITAL_INPUT"), Blockly.Lua.ORDER_NONE];
    };

    Blockly.Lua.forBlock['analog_inputs'] = function(block,generator) {
        return [block.getFieldValue("ANALOG_INPUT"), Blockly.Lua.ORDER_NONE];
    };

    var optaIp;
    var selectedSlot;
    var operationInProgress = false;

    var loadDefinition = function () {
        if (operationInProgress) return;
        setOperationLock(true);
        console.log("Loading lua!");

        var definition

        $.ajax({
            url: "http://" + optaIp + "/logic/events/" + selectedSlot,
            type: 'GET',
            processData: false,
            contentType: 'application/json',
            success: function(d){
                definition = d;
                console.log(d)
                Blockly.serialization.workspaces.load(d.blockly, workspace)
                    //workspace.zoomToFit()
                console.log("loaded")
                if (d.blockly && Object.keys(d.blockly).length > 0) {
                    setResult("Loaded slot " + selectedSlot + " successfully")
                } else {
                    setResult("Slot " + selectedSlot + " is empty")
                }
                setOperationLock(false);
            },
            error: function(xhr, type) {
                console.log("Error loading logic definition, error=" + type);
                setResult("Error loading " + selectedSlot + ", error=" + type)
                setOperationLock(false);
            }
        });
    }
    var doGenerate = function() {
        if (operationInProgress) return;
        setOperationLock(true);
        console.log("Generating lua!");
        const code = Blockly.Lua.workspaceToCode(workspace);
        console.log("Code: " + code );

        var definition = {
            ts: Date.now()/1000,
            active: false,
            lua: code,
            /* TODO we need to switch to the new JSON serialization mechanism */
            blockly: Blockly.serialization.workspaces.save(workspace)
        }

        $.ajax({
            url: "http://" + optaIp + "/logic/events/" + selectedSlot,
            type: 'PUT',
            processData: false,
            contentType: 'application/json',
            data: JSON.stringify(definition),
            success: function(d){
                console.log(d)
                setResult("Saved " + selectedSlot + " successfully")
                const hasLogic = code.trim().length > 0;
                const slotsEl = document.getElementById("slots");
                const opt = Array.from(slotsEl.options).find(o => o.value === selectedSlot);
                if (opt) opt.text = (hasLogic ? "● " : "○ ") + selectedSlot;
                setOperationLock(false);
            },
            error: function(xhr, type) {
                console.log("Error saving logic definition, error=" + type);
                setResult("Error saving logic definition for " + selectedSlot + ", error=" + type)
                setOperationLock(false);
            }
        });
        console.log(definition)
    };

    function validateHostname(hostname) {
        if (/^([a-zA-Z0-9](?:(?:[a-zA-Z0-9-]*|(?<!-)\.(?![-.]))*[a-zA-Z0-9]+)?)$/.test(hostname)) {
            return true;
        }
        return false;
    }
    function validateIPaddress(ipaddress) {
        if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
            return true;
        }
        return false;
    }

    var setResult = function(msg) {
        document.getElementById("result").innerHTML = msg
        console.log("result: " + msg)
        window.setTimeout(clearResult, 5000)
    }

    var clearResult = function() {
        document.getElementById("result").innerHTML = "&nbsp;";
    }
    var slotSelected = function(name) {
      console.log("Selected slot: " + name)
      if(name !== "") {
          selectedSlot = name
          enableControls(true)
      } else {
          selectedSlot = ""
          enableControls(false)
      }

    }

    var changeIp = function(ip) {
        if(!validateIPaddress(ip) && !validateHostname(ip)) {
            console.log("Invalid host or ip.");
            return;
        }
        console.log("ip: " + ip);
        var el = document.getElementById("optaip");
        console.log("el: " + el);
        el.value = ip;
        optaIp = ip;

        console.log(el.value)
        loadSlots(ip);
    }

    var enableControls = function(hmm) {
        document.getElementById("generateCode").disabled = !hmm;
        document.getElementById("loadCode").disabled = !hmm;
    }
    var setOperationLock = function(locked) {
        operationInProgress = locked;
        document.getElementById("generateCode").disabled = locked;
        document.getElementById("loadCode").disabled = locked;
    };
    var loadSlots = function(ip) {
         setResult("load slots for " + ip)
        var slots = document.getElementById("slots")
        slots.innerHTML = '<option value="">Logic slots</option>'
        selectedSlot = "";
        enableControls(false);

        $.ajax( {
          url: "http://" + ip + "/logic/events",
          type: 'GET',
          processData: false,
            timeout:3000,
          contentType: 'application/json',
          success: function(d){
              console.log(d)
              setResult("")
              var slots = document.getElementById("slots")
              for (var prop in d) {
                  console.log(prop)
                  const hasLogic = !!d[prop];
                  const label = (hasLogic ? "● " : "○ ") + prop;
                  const newOption = new Option(label, prop);
                  slots.add(newOption);
              }
              setResult("Loaded logic slots from Link70")
          },
          error: function(xhr, type) {
              setResult("Error getting logic slots, error=" + type)
          }
      } )
    };

    var updateIp = function(ip) {
        if(!validateIPaddress(ip) && !validateHostname(ip)) {
            console.log("Invalid host or ip.");
            setResult("Invalid host or ip.")
            return;
        }
        console.log("ip: " + ip);
        var el = document.getElementById("optaip");
        console.log("el: " + el);
        el.value = ip;
        optaIp = ip;

        console.log(el.value)
        loadSlots(ip);
    }

    var ps = function() {
        const ip = window.location.hash.substring(1);
        updateIp(ip)
    }

    window.addEventListener('hashchange', function() {
        // Your JavaScript code here
        const currentHash = window.location.hash;
        console.log('The anchor has changed to: ' + currentHash);

        ps()

    });
    // Register the definition.
    Blockly.common.defineBlocks(definitions);

    // The toolbox gets passed to the configuration options during injection.
    const workspace = Blockly.inject('blocklyDiv', { renderer: 'zelos',
        zoom:
            {controls: true,
                wheel: true,
                startScale: 1.0,
                maxScale: 3,
                minScale: 0.3,
                scaleSpeed: 1.2,
                pinch: true},
        toolbox: toolbox});

    workspace.getFlyout().setAutoClose(false);

    enableControls(false);

    $(document).ready(ps);

    </script>
</body>
</html>
